package com.dp.mallchat.transaction.service;

import cn.hutool.core.date.DateUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.dp.mallchat.common.utils.JsonUtils;
import com.dp.mallchat.transaction.dao.SecureInvokeRecordDao;
import com.dp.mallchat.transaction.domain.InvokeStateEnum;
import com.dp.mallchat.transaction.domain.dto.SecureInvokeDTO;
import com.dp.mallchat.transaction.domain.entity.SecureInvokeRecord;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.CollectionUtils;

import javax.validation.constraints.NotNull;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;

/**
 * @Author: dupeng
 * @CreateTime: 2024-07-01  14:02
 * @Description: TODO
 */
@Service
@Slf4j
public class SecureInvokeRecordService {

    @Autowired
    private Executor executor;

    @Autowired
    private SecureInvokeRecordDao secureInvokeRecordDao;

    public static final double RETRY_INTERVAL_MINUTES = 2D;


    @Scheduled(cron = "*/5 * * * * ?")
    public void retry() {
        List<SecureInvokeRecord> retryList = secureInvokeRecordDao.getRetryList();
        if (CollectionUtils.isEmpty(retryList)) {
            return;
        }
        for (SecureInvokeRecord secureInvokeRecord : retryList) {
            asyncInvokeTarget(secureInvokeRecord);
        }
    }


    public void invoke(SecureInvokeRecord record, boolean async) {
        //非事务，不做任何保证,此项判断目前可加可不加，现入口已做校验
        boolean actualTransactionActive = TransactionSynchronizationManager.isActualTransactionActive();
        if (!actualTransactionActive) {
            return;
        }
        //保存记录
        save(record);
        //开启新事物执行
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCommit() {
                if (async) {
                    //执行目标方法
                    asyncInvokeTarget(record);
                } else {
                    invokeTarget(record);
                }
            }
        });
    }

    public void save(SecureInvokeRecord secureInvokeRecord) {
        secureInvokeRecordDao.save(secureInvokeRecord);
    }

    /**
     * 异步执行目标方法
     *
     * @param record
     */
    private void asyncInvokeTarget(SecureInvokeRecord record) {
        executor.execute(() -> {
            invokeTarget(record);
        });
    }

    /**
     * 执行目标方法
     *
     * @param record
     */
    private void invokeTarget(SecureInvokeRecord record) {
        SecureInvokeDTO secureInvokeJson = record.getSecureInvokeDTO();

        try {
            SecureInvokeHolder.setSecureInvokeHolder();
            Class<?> beanClass = Class.forName(secureInvokeJson.getClassName());
            Object bean = SpringUtil.getBean(beanClass);
            List<Class<?>> parameterClasses = stringToListClass(secureInvokeJson.getParameterTypes());
            Method method = beanClass.getMethod(secureInvokeJson.getMethodName(), parameterClasses.toArray(new Class[]{}));
            Object[] args = getArgs(record.getSecureInvokeDTO(), parameterClasses);
            //执行目标方法
            method.invoke(bean, args);
            //执行成功更新状态(执行已成功，删除本地记录)
            removeById(record);
        } catch (Exception e) {
            log.error("execute secure invoke failed, reason：", e);
            //失败更新下次执行时间等信息
            updateNextExecuteTime(record, e.getMessage());
        }finally {
            //清空事务状态
            SecureInvokeHolder.invokeHolder();
        }
    }

    /**
     * String转class
     *
     * @param parameterTypes 多种参数类型
     * @return
     */
    private List<Class<?>> stringToListClass(String parameterTypes) {
        parameterTypes = parameterTypes.replace("\"", "");
        String[] split = parameterTypes.split(",");
        //清除首[ 尾]
        split[0] = split[0].substring(1);
        split[split.length - 1] = split[split.length - 1].substring(0, split[split.length - 1].length() - 1);
        //转为Class
        return Arrays.stream(split).map(name -> {
            try {
                return Class.forName(name);
            } catch (ClassNotFoundException e) {
                log.error("class not found, class name: {}", name);
            }
            return null;
        }).collect(Collectors.toList());
    }

    /**
     * 解析入参
     *
     * @param secureInvokeDTO   类信息
     * @param parameterClasses  方法参数类型
     * @return
     */
    @NotNull
    private Object[] getArgs(SecureInvokeDTO secureInvokeDTO, List<Class<?>> parameterClasses) {
        JsonNode jsonNode = JsonUtils.toJsonNode(secureInvokeDTO.getArgs());
        Object[] args = new Object[jsonNode.size()];
        for (int i = 0; i < jsonNode.size(); i++) {
            Class<?> aClass = parameterClasses.get(i);
            args[i] = JsonUtils.nodeToValue(jsonNode.get(i), aClass);
        }
        return args;
    }

    private void removeById(SecureInvokeRecord record) {
        secureInvokeRecordDao.removeById(record.getId());
    }

    /**
     * 更新记录下次执行时间等信息，
     * 重试次数>最大重试次数,停止此数据的执行
     *
     * @param record   数据
     * @param message 堆栈信息
     */
    private void updateNextExecuteTime(SecureInvokeRecord record, String message) {
        Integer nextRetryNum = record.getRetryTimes() + 1;

        SecureInvokeRecord update = new SecureInvokeRecord();
        update.setId(record.getId());
        if (nextRetryNum > record.getMaxRetryTimes()) { //大于数据最大重试次数，宣布这条数据为失败状态，后续不再重试
            update.setStatus(InvokeStateEnum.FAIL.getType());
        } else {
            update.setRetryTimes(nextRetryNum);
            update.setFailReason(message);
            update.setNextRetryTime(getNextRetryTime(nextRetryNum));   //2m 4m 8m
        }
        secureInvokeRecordDao.updateById(update);
    }

    private Date getNextRetryTime(Integer nextRetryNum) {
        double waitMinutes = Math.pow(RETRY_INTERVAL_MINUTES, nextRetryNum);//重试时间指数上升 2m 4m 8m 16m
        return DateUtil.offsetMinute(new Date(), (int) waitMinutes);
    }

}
